package com.example.springbootjavacode;

import javax.tools.*;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author: wukangrong
 * @date: 2021/1/8 11:35
 * @description:
 */
public class JavaCodeTest {
    private static final Pattern CLASS_PATTERN = Pattern.compile("class\\s+([$_a-zA-Z][$_a-zA-Z0-9]*)\\s*");

    private static Map<String, JavaFileObject> fileObjects = new ConcurrentHashMap<>();
    public static void main( String[] args ) {
        StringBuilder code = new StringBuilder();

        String filePath = "E:\\importproject\\springboot-javacode\\src\\main\\resources\\javacode\\ControllerTest.java";
        File codeFile = new File(filePath);
        if (codeFile.exists() &&  codeFile.isFile()) {
            try {
                FileInputStream codeStream = new FileInputStream(codeFile);
                BufferedReader codeReader = new BufferedReader(new InputStreamReader(codeStream));
                codeReader.readLine();
                String temp = codeReader.readLine();
                while (temp != null) {
                    code.append(temp).append("\n");
                    temp = codeReader.readLine();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else {
            System.out.println("非有效文件路径：【" + filePath + "】");
        }
        System.out.println(code.toString());

        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        DiagnosticCollector<JavaFileObject> collector = new DiagnosticCollector<>();
        JavaFileManager javaFileManager = new MyJavaFileManager(compiler.getStandardFileManager(collector, null, null));

        List<String> options = new ArrayList<>();
        options.add("-target");
        options.add("1.8");


        Matcher matcher = CLASS_PATTERN.matcher(code);
        String cls;
        if(matcher.find()){
            cls = matcher.group(1);
        } else{
            throw new IllegalArgumentException("No such class name in " + code);
        }

        JavaFileObject javaFileObject = new MyJavaFileObject(cls, code.toString());
        Boolean result = compiler.getTask(null, javaFileManager, collector, options, null, Collections.singletonList(javaFileObject)).call();

        System.out.println("类编译结果：" + result);
        ClassLoader classloader = new MyClassLoader();

        Class<?> clazz = null;
        try {
            clazz  = classloader.loadClass(cls);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        if (Objects.isNull(clazz)) {
            System.out.println("未找到类:" + cls);
            return;
        }

        Method method = null;
        try {
            method = clazz.getMethod("getPersonIds");
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        if (Objects.isNull(method)) {
            System.out.println("未找到方法");
            return;
        }
        try {
            method.invoke(clazz.newInstance());
        } catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
            e.printStackTrace();
        }
    }

    public static class MyClassLoader extends ClassLoader {

        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            JavaFileObject fileObject = fileObjects.get(name);
            if(fileObject != null){
                byte[] bytes = ((MyJavaFileObject)fileObject).getCompiledBytes();
                return defineClass(name, bytes, 0, bytes.length);
            }
            try{
                return ClassLoader.getSystemClassLoader().loadClass(name);
            } catch (Exception e){
                return super.findClass(name);
            }
        }
    }

    public static class MyJavaFileObject extends SimpleJavaFileObject {
        private String source;
        private ByteArrayOutputStream outPutStream;


        public MyJavaFileObject(String name, String source) {
            super(URI.create("String:///" + name + Kind.SOURCE.extension), Kind.SOURCE);
            this.source = source;
        }

        public MyJavaFileObject(String name, JavaFileObject.Kind kind){
            super(URI.create("String:///" + name + kind.extension), kind);
            source = null;
        }

        @Override
        public CharSequence getCharContent(boolean ignoreEncodingErrors){
            if(source == null){
                throw new IllegalArgumentException("source == null");
            }
            return source;
        }

        @Override
        public OutputStream openOutputStream() throws IOException {
            outPutStream = new ByteArrayOutputStream();
            return outPutStream;
        }

        public byte[] getCompiledBytes(){
            return outPutStream.toByteArray();
        }
    }

    public static class MyJavaFileManager extends ForwardingJavaFileManager<JavaFileManager> {
        protected MyJavaFileManager(JavaFileManager fileManager) {
            super(fileManager);
        }

        @Override
        public JavaFileObject getJavaFileForInput(JavaFileManager.Location location, String className, JavaFileObject.Kind kind) throws IOException {
            JavaFileObject javaFileObject = fileObjects.get(className);
            if(javaFileObject == null){
                super.getJavaFileForInput(location, className, kind);
            }
            return javaFileObject;
        }

        @Override
        public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location, String qualifiedClassName, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
            JavaFileObject javaFileObject = new MyJavaFileObject(qualifiedClassName, kind);
            fileObjects.put(qualifiedClassName, javaFileObject);
            return javaFileObject;
        }
    }
}
